package audio;

import java.io.File;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.dom4j.Document;
import org.dom4j.Element;

import java.net.URLDecoder;



public class Searcher {

	public static void main(String[] args) {
		String wavFile = "";
		if(args.length == 1){
			//System.out.println("Usage <input wavfile>");
			wavFile = args[0];
		}else{
			wavFile = "E:\\test samples\\海贼王1[Part 5].wav";
		}
		
		//test
		execute(wavFile);
	}

	@SuppressWarnings("unchecked")
	public static String[][] execute(String  wavFile) {
		// TODO Auto-generated method stub
		String[][] result = null;
		try{
			System.out.println(wavFile);
//			wavFile = URLDecoder.decode(wavFile);
//			System.out.println(wavFile);
			File audio = new File(wavFile);
			if(!audio.exists()) return null;
			
			long startTime=System.currentTimeMillis();   //获取开始时间
			
			//To extract target audio MFCC feature
			System.out.println("extract target audio MFCC2 feature...");
			//generate audio mfcc2 in audio's path
			String dstMFCC2File = MFCC2.getMFCC2(audio.getAbsolutePath(), audio.getParent());
			System.out.println("extract target audio MFCC2 feature end.");
			
			long endTime=System.currentTimeMillis();   //获取开始时间
			System.out.println("extract time:" + (endTime - startTime) + "ms");
			
			System.out.println("search match audios...");
			startTime=System.currentTimeMillis();   //获取开始时间
			//search scope
			//String path =Thread.currentThread().getContextClassLoader().getResource("").getPath();
			String srcMFCC2Path = Util.classPath + "MFCC2";//4000 0;
			
			List<Match> matchs= getAllMatchs(dstMFCC2File,srcMFCC2Path);
			
			ComparatorMacth comparator = new ComparatorMacth();
			Collections.sort(matchs, comparator);
			result = new String[matchs.size()][4];
			int i = 0;
			for(Match match : matchs){
				result[i][0] = Double.toString(match.getConfidence());
				result[i][1] = match.getMatchPath();
				result[i][2] = Double.toString(match.getStartTime());
				result[i][3] = Double.toString(match.getEndTime());
				System.out.print(i + 1 + " ");
				System.out.print("difference:" + result[i][0]);
				System.out.print(" filename:" + result[i][1]);
				System.out.print(" start:" + result[i][2] + "s");
				System.out.println(" end:" + result[i][3] + "s");
				i++;
			}
			System.out.println("search match audios end.");
			endTime = System.currentTimeMillis();   //获取开始时间
			System.out.println("search time:" + (endTime - startTime) + "ms");
			
		}catch(Exception e){
			System.out.println("seach failure.");
		}
		return result;
	}
	

	private static List<Match> getAllMatchs(String dstMFCC2File,String srcMFCC2Path){
		System.out.println(dstMFCC2File);
		System.out.println(srcMFCC2Path);
		Document dstDoc = DOM4JParser.parserXml(dstMFCC2File);
		double[][] dstMFCC2 = getMFCC2Array(dstDoc);
		
		double durationTime = dstMFCC2[dstMFCC2.length-1][13];
		
		List<Match> list = new ArrayList<Match>();
		File srcPathFile = new File(srcMFCC2Path);
		
		if(srcPathFile.exists() && srcPathFile.isDirectory()){
			File[] files = srcPathFile.listFiles();
			for (File file : files) {
				Match match = getMatchInfo(dstMFCC2,file);
				//save match sections end time
				match.setEndTime(round(match.getStartTime() + durationTime, 2, BigDecimal.ROUND_FLOOR));
				list.add(match);
			}
		}
		return list;
	}
	
	private static Match getMatchInfo(double[][] dstMFCC2,File srcFile){
		Match match = new Match();
		String srcMFCC2File = srcFile.getAbsolutePath();
		
		//set match file path
		//match.setMatchPath(srcMFCC2File);
		match.setMatchPath(srcFile.getName().replace(".xml",".wav"));
		
		Document srcDoc = DOM4JParser.parserXml(srcMFCC2File);
		double[][] srcMFCC2 = getMFCC2Array(srcDoc);

		//set match confidence and start time
		fillMatchOtherInfo(dstMFCC2,srcMFCC2,match);
		
		return match;
	}
	
	public static double[][] getMFCC2Array(Document srcDoc){
		// TODO Auto-generated method stub
		double[][] mfcc2 = null;
		Element rootElement = srcDoc.getRootElement();
		Element dataset = rootElement.element("data_set");
		@SuppressWarnings("unchecked")
		List<Element> sectionList = dataset.elements("section");
		
		if (sectionList != null) {
			int len = sectionList.size();
			//last two save start and end time
			mfcc2 = new double[len][14];
			Element[] section = new Element[len];
			// get mfcc without first two section and last two section 
			for (int i = 0; i < len; i++) {
				// current MFCC
				section[i] = sectionList.get(i);
				mfcc2[i] = getValues(section[i]);
			}
		}
		return mfcc2;
	}
	
	private static double[] getValues(Element section) {

		double values[] = null;

		Element feture = section.element("feature");
		@SuppressWarnings("unchecked")
		List<Element> valueList = feture.elements("v");

		if (valueList != null) {
			//last two save start and end time
			values = new double[14];
			for (int i = 0; i < 12; i++) {
				Element value = valueList.get(i);
				if (value.getText() != null) {
					values[i] = Double.parseDouble(value.getText());
				}
			}
			//last two save start and end time
			values[12] = Double.parseDouble(section.attributeValue("start"));
			values[13] = Double.parseDouble(section.attributeValue("stop"));
		}
		return values;
	}

	private static double getFrobenius(double[][] dstMFCC2,double[][] srcMFCC2){
		double minfrobenius = Double.MAX_VALUE;
		if(dstMFCC2.length <= srcMFCC2.length){
			for(int i = 0;i < srcMFCC2.length - dstMFCC2.length + 1;i++){
				double tmpFrobenius = 0;
				for(int j = 0;j < dstMFCC2.length;j++){
					for(int k = 0;k < 12;k++){
						tmpFrobenius += (dstMFCC2[j][k] - srcMFCC2[j + i][k]) * (dstMFCC2[j][k] - srcMFCC2[j + i][k]);
					}
					//发现已经不是最小的，退出本次循环
					if(Math.sqrt(tmpFrobenius) > minfrobenius) break;
				}
				tmpFrobenius = Math.sqrt(tmpFrobenius);
				if (tmpFrobenius < minfrobenius){
					minfrobenius = tmpFrobenius;
				}
			}
		}
		
		//return minfrobenius;
		return round(minfrobenius, 2, BigDecimal.ROUND_FLOOR);
	}

	private static void fillMatchOtherInfo(double[][] dstMFCC2,double[][] srcMFCC2,Match match){
		double minfrobenius = Double.MAX_VALUE;
		int startSection = 0;
		if(dstMFCC2.length <= srcMFCC2.length){
			for(int i = 0;i < srcMFCC2.length - dstMFCC2.length + 1;i++){
				double tmpFrobenius = 0;
				for(int j = 0;j < dstMFCC2.length;j++){
					for(int k = 0;k < 12;k++){
						tmpFrobenius += (dstMFCC2[j][k] - srcMFCC2[j + i][k]) * (dstMFCC2[j][k] - srcMFCC2[j + i][k]);
					}
					//发现已经不是最小的，退出本次循环
					//if(Math.sqrt(tmpFrobenius) > minfrobenius) break;
				}
				tmpFrobenius = Math.sqrt(tmpFrobenius);
				if (tmpFrobenius < minfrobenius){
					minfrobenius = tmpFrobenius;
					startSection = i;
				}
			}
		}
		
		match.setConfidence(round(minfrobenius, 2, BigDecimal.ROUND_FLOOR));
		match.setStartTime(round(srcMFCC2[startSection][12], 2, BigDecimal.ROUND_FLOOR));
	}
	
	 public static double round(double value, int scale, 
             int roundingMode) {   
        BigDecimal bd = new BigDecimal(value);   
        bd = bd.setScale(scale, roundingMode);   
        double d = bd.doubleValue();   
        bd = null;   
        return d;   
    }   
}
